Skip to content

fix(datasource,runtime): declare the guarded optional-driver loads as optional peers - #13401

Merged
os-elon merged 1 commit into
mainfrom
claude/issue-12943-optional-driver-peers
Aug 30, 2026
Merged

fix(datasource,runtime): declare the guarded optional-driver loads as optional peers#13401
os-elon merged 1 commit into
mainfrom
claude/issue-12943-optional-driver-peers

Conversation

@os-elon

@os-elon os-elon commented Aug 30, 2026

Copy link
Copy Markdown
Collaborator

Fixes #12943

Implements the maintainer's ruling of 2026-08-29 (option 甲, verbatim 「同意」) as one PR across service-datasource and runtime, exactly as that ruling directs.

What changed

Five guarded await import(...) loads of workspace driver packages sat in published src/** with no manifest declaration an installing consumer could see. Each is now an optional peerDependencies entry plus peerDependenciesMeta: { optional: true } — the form @objectstack/cli already uses for driver-turso:

package now declares as optional peer
@objectstack/service-datasource driver-turso, driver-sqlite-wasm, driver-mongodb
@objectstack/runtime driver-turso

Nothing is installed and no code path changes: an optional peer declares a relationship that already existed at runtime, so npm ls, a lockfile, an audit tool and a reader of the manifest can all see the driver a datasource may ask for, instead of learning about it only by hitting the failure arm. driver-mongodb and driver-sqlite-wasm were devDependencies of service-datasource, which tells a consumer nothing; driver-turso was in no section of either manifest.

⛔ The sixth occurrence of the same shape (rest to objectql, degrading to 501 NOT_IMPLEMENTED) is deliberately untouched: rest's non-coupling to the data engine is a stated architectural position, not a hygiene gap.

The five optional-runtime-probe rows the declarations make stale are deleted from scripts/check-undeclared-dep-imports.mjs — as that gate demands ("once the manifest declares it, the finding is gone and the row is stale, which is RED") and only as far as it demands. ⛔ No row was relaxed to quiet the gate. Of the ledger's 7 rows, 2 remain: rest to objectql, and the unrelated type-only row.

The pin conversions, and a population larger than the card's

pnpm links an optional workspace peer, so a pin that reached its missing-package arm because the package did not resolve stops doing so. The card named one such pin. Measured: there are three, and two of them are in packages/runtime:

pin how it depended on the accident
service-datasource/src/__tests__/default-datasource-driver-factory.test.ts The named one. Its own notice: "the pin above needs a stubbed import instead."
runtime/src/turso-driver-factory.convergence.test.ts A verbatim twin of that notice, calling the open-core factory bare from runtime.
runtime/src/standalone-stack.libsql.test.ts Its own alarm: "(Should the package ever become a dependency of this one, this case turns red and names exactly why in this comment.)"

⛔ None is deleted or weakened. All three now stage the absence with vi.doMock and the resolver's own ERR_MODULE_NOT_FOUND, keeping every assertion — including the typed-identity ones behind serve.ts's e instanceof MissingDriverPackageError boot-fatality branch.

vi.doMock without vi.resetModules(), deliberately. A reset re-evaluates missing-driver-package-error.js alongside the factory, so the arm raises a different class object and instanceof is false for a perfectly correct error — measured, in an earlier form of this branch, as expected MissingDriverPackageError … to be an instance of MissingDriverPackageError. Without the reset the identity assertions stay pointed at the binding the host actually holds, which is the only reason they are worth asserting. The two #7385 sibling arms keep using the reset-based raiseWithPackageAbsent helper, untouched — measured, they never relied on the accident, because both their packages already resolve here.

Prose in five places stated the opposite of the new manifests (e.g. *"is not declared by @objectstack/runtime at all"*) and is corrected in runtime/src/turso-driver-factory.ts, service-datasource/src/default-datasource-driver-factory.tsand one comment incli/src/utils/storage-driver.test.ts`.

Verification

Gate family derived at edit time with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (11 paths, never a copied list). Exit codes captured before any pipe. Union re-run on the final commit 69c27808a.

  • Full-repo lintpnpm lint (eslint . --no-inline-config), not a narrowed scan: exit 0.
  • Derived gate family, all exit 0: undeclared-dep-imports (+ --self-test), nul-bytes, agent-test-spelling, bash32-floor, changeset-gate-self-tests, cli-command-ids, cross-package-test-inputs, doc-authoring, entry-guard, i18n, i18n-coverage, llms-txt, logger-receiver-detach, objectql-double-limit, objectui-changeset, override-consistency, page-declaration-shape, parse-guard, pm-half-states, pnpm-filter-targets, published-files, slot-lookup, test-source-alias, type-source-resolution, watch-hint-literal, where-matcher, query-options-erasure, engine-double-contract, type-check-coverage, type-check-debt, pm-dispatch-gates, empty-changeset, dual-build-cjs-loads.
    • i18n / i18n-coverage / type-check-debt first answered PREREQUISITE NOT MET ("Nothing was checked") on an unbuilt tree — recorded as NOT MEASURED, never as red — and are green above only after the full closure build turbo run build --filter=./packages/* --filter=./packages/*/*.
    • Gate verdict quoted from the gate itself: ✓ check:undeclared-dep-imports: 78 workspace packages …, 2 ledger row(s), all evidence intact. and check-type-check-coverage --re-measure: OK — 30 ledger entr(ies) re-measured, 1558 raw tsc error(s) total, none above its recorded number. ⛔ No DEBT/TEST_DEBT entry was added or raised.
  • Suites: service-datasource 585/585 in 27 files; runtime 2968/2968 in 201 files; cli storage-driver.test.ts 36/36. Typechecks: service-datasource, runtime, cli all exit 0.
    • ⚠️ Declared narrowing, and one honest NOT MEASURED. runtime's tsconfig.json excludes **/*.test.ts, so rt-typecheck green says nothing about the two runtime test files edited here (--listFiles hits: 0). Measured separately with a temporary project that includes them: both files are in the program (hits: 1 each) and zero errors are attributed to either; the 217 errors that program reports are pre-existing residue in runtime's other excluded test files, which is why the exclusion exists and is already ledgered. The probe config was removed and its removal proven (git status clean). For cli only the one edited file was run, its change being a comment.

Reverse verification

Predicted direction, declared before running: with the five rows deleted, removing a declaration can no longer produce a stale-row red — it must produce an unledgered-finding red. Observed exactly that. Dropping driver-turso from service-datasource's peers:

✗ check:undeclared-dep-imports: 1 undeclared workspace import(s)
  • @objectstack/service-datasource <- @objectstack/driver-turso (not declared at all)
      packages/services/service-datasource/src/default-datasource-driver-factory.ts:1239  [dynamic]

The complementary direction was measured too, before the rows were cut: with the declarations landed and the rows still present, the gate named all five as stale and refused. So the deletion is a measured requirement, not an inference.

Ablation — proof the stub still ENTERS the arm (the failure mode that turns a real test into a decoration). Mutating the turso catch in default-datasource-driver-factory.ts to rethrow the raw resolver error turned exactly one test red — the converted pin — on expected '[vitest] There was an error when mock…' to contain 'npm install @objectstack/driver-turso', with the other 49 still passing. Resolution path stated: the test imports its subject by a relative specifier inside the same package, so vitest reads src/*.ts and no rebuild leg applies. Mutation proven on disk (marker count 1, blob 7470571c to 3d275d2b); restore proven by state (blob back to 7470571c, marker count 0, git diff HEAD empty, git status clean), under an EXIT INT TERM trap with absolute paths.

Measured readings that differ from the card

  1. The card's load-bearing claim is CONFIRMED, against my own prediction. I predicted no link, on the documented ground that auto-install-peers installs missing non-optional peers. pnpm install says otherwise: pnpm-lock.yaml gains link:../../drivers/driver-turso under both importers, and the symlinks exist. Triage had flagged that it never reproduced this; it reproduces.
  2. The #7385 siblings do not rely on the accident — they have always staged absence, because their packages already resolve here. The extra work was in packages/runtime instead.
  3. runtime's occurrence is the same class: a guarded dynamic import() of an optional package answering with the same typed error. Only the thunk replacement is host-specific.
  4. The probe-row count is 6, not 11. LEDGER holds 7 rows, 6 of kind optional-runtime-probe (controls: a nonsense kind returns 0; type-only returns its 1 row). Eleven is the count of text occurrences of the string, which includes the header, LEDGER_KINDS, two diagnostics and a self-test fixture.

One consequence recorded rather than hidden

Declaring the peer adds a workspace edge service-datasource to driver-turso, and driver-turso devDepends on @objectstack/verify to runtime to service-datasource — a cycle. It is invisible to the real build path: turbo's package graph does not read peerDependencies (measured — the 20-task graph for service-datasource#build contains no driver-turso), so pnpm build and CI are unaffected. It is visible to pnpm --filter 'PKG^...', which does read peer edges and now selects a cyclic superset with no topological order. Building through turbo is the workaround used here. Flagged for review rather than decided.


Generated by Claude Code

… optional peers (#12943)

Five guarded `await import(...)` loads of workspace driver packages sat in
published `src/**` with no manifest declaration an installing consumer could
see. `@objectstack/service-datasource` reaches driver-turso, driver-sqlite-wasm
(two sites) and driver-mongodb; `@objectstack/runtime` reaches driver-turso.
Two of them were devDependencies, which tells a consumer nothing; driver-turso
was in no section of either manifest.

Each is now an optional `peerDependencies` entry plus
`peerDependenciesMeta: { optional: true }` — the form `@objectstack/cli`
already uses for driver-turso. Nothing is installed and no code path changes:
an optional peer declares a relationship that already existed at runtime.

The `rest` to `objectql` occurrence of the same shape is deliberately left
alone; rest's non-coupling to the data engine is a stated architectural
position, not a hygiene gap.

pnpm links an optional workspace peer, so three test pins that had reached
their missing-package arm with no stub stop doing so. Each had said in advance
what to do about it. All three now stage the absence with `vi.doMock` and keep
every assertion, including the typed-identity ones behind serve.ts's
`e instanceof MissingDriverPackageError` boot-fatality branch. The five
`optional-runtime-probe` ledger rows the declarations make stale are deleted,
as that gate demands and only as far as it demands.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012WkdHQwHr2KQmaX7P1BHzi
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

2 anchor(s) derived from 2 changed package(s); no hand-written page names any of them. ⚠️ 3 changed file(s) yielded no anchor (packages/runtime/package.json, packages/services/service-datasource/package.json, packages/services/service-datasource/src/default-datasource-driver-factory.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

What this run could not see
  • 3 changed file(s) yielded no anchor (packages/runtime/package.json, packages/services/service-datasource/package.json, packages/services/service-datasource/src/default-datasource-driver-factory.ts) — pages documenting those are invisible to this run
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 23 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json a68c61267f9ae45b43974d9a3992952067c26a3fpackageMentionDocs.

Which tree this was computed on

This run read content/docs from cced02902f279daee5ca98d8e6c85d9dd089526b — the merge of head 69c27808a2c3501ab887446393936fb0fee650d8 into base a68c61267f9ae45b43974d9a3992952067c26a3f, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin cced02902f279daee5ca98d8e6c85d9dd089526b && git checkout cced02902f279daee5ca98d8e6c85d9dd089526b
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin a68c61267f9ae45b43974d9a3992952067c26a3f 69c27808a2c3501ab887446393936fb0fee650d8 && git checkout -B drift-repro a68c61267f9ae45b43974d9a3992952067c26a3f && git merge --no-ff 69c27808a2c3501ab887446393936fb0fee650d8

node scripts/docs-audit/affected-docs.mjs --json a68c61267f9ae45b43974d9a3992952067c26a3f

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@github-actions github-actions Bot added dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation tests tooling labels Aug 30, 2026
@os-elon
os-elon marked this pull request as ready for review August 30, 2026 09:39
@os-elon
os-elon enabled auto-merge August 30, 2026 09:39
@os-elon
os-elon added this pull request to the merge queue Aug 30, 2026
Merged via the queue into main with commit 090f230 Aug 30, 2026
35 checks passed
@os-elon
os-elon deleted the claude/issue-12943-optional-driver-peers branch August 30, 2026 10:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

2 participants